package pl.edu.mimuw.irs.core.model.analysis;

import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import pl.edu.mimuw.irs.core.model.analysis.exception.InternalAnalysisException;
import pl.edu.mimuw.irs.core.model.analysis.exception.InternalAnalysisException.Cause;
import pl.edu.mimuw.irs.core.model.analysis.subnet.ActivitySubnet;
import pl.edu.mimuw.irs.core.model.analysis.subnet.AtomicSubnet;
import pl.edu.mimuw.irs.core.model.analysis.subnet.HiringSubnet;
import pl.edu.mimuw.irs.core.model.analysis.subnet.ResourcesWorkflow;
import pl.edu.mimuw.irs.core.model.design.IRSActivity;
import pl.edu.mimuw.irs.core.model.design.IRSNodeInterface;
import pl.edu.mimuw.irs.core.model.design.IRSPlace;
import pl.edu.mimuw.irs.core.model.design.IRSResourceSet;
import pl.edu.mimuw.irs.core.model.design.IRSTransition;
import pl.edu.mimuw.irs.core.model.design.IRSWorkflow;
import pl.edu.mimuw.irs.core.model.design.processing.Requirement;
import pl.edu.mimuw.irs.core.model.design.processing.Resource;
import pl.edu.mimuw.irs.core.model.petrinet.Arc;
import pl.edu.mimuw.irs.core.model.petrinet.INode;
import pl.edu.mimuw.irs.core.model.petrinet.Place;
import pl.edu.mimuw.irs.core.model.petrinet.Transition;
import pl.edu.mimuw.irs.core.util.Converter;

/**
 * @author Jakub Rauch
 * Created on: 2009-05
 */
public class AnalysisWorkflow extends ResourcesWorkflow {

	// *** Attributes

	private Map<IRSActivity, ActivitySubnet> activityMap;
	private AtomicSubnet atomicNet;
	public Map<IRSActivity, ActivitySubnet> getActivityMap() {
		if (this.activityMap == null) activityMap = 
			new LinkedHashMap<IRSActivity, ActivitySubnet>();
		return activityMap;
	}
	public void setActivityMap(
			Map<IRSActivity, ActivitySubnet> activityMap) {
		this.activityMap = activityMap;
	}
	public AtomicSubnet getAtomicNet() {
		return atomicNet;
	}
	public void setAtomicNet(AtomicSubnet atomicNet) {
		this.atomicNet = atomicNet;
	}
	
	// *** Constructors

	public AnalysisWorkflow(IRSWorkflow workflow) {
		
		super(workflow);
		
		Set<IRSActivity> activitySet = getActivities(workflow.getRootPlace());
		Map<IRSActivity, ActivitySubnet> activityMap = getActivityMap();
		
		for (IRSActivity activity : activitySet) {
			ActivitySubnet subnet = new ActivitySubnet(activity);
			loadNet(subnet);
			activityMap.put(activity, subnet);
		}
		
		setAtomicNet(getWorkflow().getRootPlace().getAtomicWorkflow());
		
		addPlaces(getAtomicNet().getPlaces());
		addTransitions(getAtomicNet().getTransitions());
		
		// replace connections between places and activities with extended activities structure
		// still - without resource getting mechanism
		linkActivitiesToGlobal();
		
		// create resource getting (hiring) mechanism for every activity.
		linkActivitiesToResources();
		
		// connect in and out of constructed workflow to existing initialize and finalize.
		linkInOut();
		
		// initialize
		getIn().addTokens(1);
	}
	
	// *** Methods

	private void linkInOut() {
		addArc(getInitialize(), getAtomicNet().getPlacesMap().get(getWorkflow().getIn()));
		addArc(getAtomicNet().getPlacesMap().get(getWorkflow().getOut()), getFinalize());
	}
	
	private void linkActivitiesToGlobal() {
		Map<IRSPlace, Place> placesMap = getAtomicNet().getPlacesMap();
		Map<IRSTransition, Transition> transitionsMap = getAtomicNet().getTransitionsMap();
		
		for (Entry<IRSActivity, ActivitySubnet> entry : getActivityMap().entrySet()) {
			
			// Manage activity entrance
			
			List<Arc> needlessArcs = new LinkedList<Arc>();
			for (Arc arc : entry.getKey().getParent().getIncomingArcs()) {
				// We use getParent(), because this IRSActivity contains incoming/outgoing arcs from resource - we
				// don't need that in this place.
				IRSPlace sourcePlace = Converter.asIRSPlace(arc.getPlace());
				List<IRSNodeInterface> atomicSource = getAtomicOutputChildren(sourcePlace);
				List<IRSNodeInterface> atomicTarget = getAtomicInputChildren(entry.getKey());
				// Delete all connections between these atomic nodes.
				// These connections will be replaced with Activity structure.
				
				// Get arcs to be deleted
				needlessArcs.addAll(getNeedlessArcs(atomicSource, atomicTarget));

				// Adding source activity structure (before hiring)
				for (IRSNodeInterface source : atomicSource) {
					IRSPlace atomicSourcePlace = Converter.asIRSPlace(source);
					Place atomicPlace = placesMap.get(atomicSourcePlace);
					if (atomicPlace == null) 
						throw new InternalAnalysisException(Cause.ATOMIC_PLACE_NOT_CREATED, 
								atomicSourcePlace.toString());
					addArc(atomicPlace, entry.getValue().getBegin(), arc.getWeight());
				}

				// Adding source activity structure (after hiring)
				for (IRSNodeInterface target : atomicTarget) {
					IRSTransition atomicTargetTransition = Converter.asIRSTransition(target);
					Transition atomicTransition = transitionsMap.get(atomicTargetTransition);
					if (atomicTransition == null) 
						throw new InternalAnalysisException(Cause.ATOMIC_TRANSITION_NOT_CREATED, 
								atomicTargetTransition.toString());
					addArc(entry.getValue().getBeforeWork(), atomicTransition, arc.getWeight());
				}
			}
			
			for (Arc arc : needlessArcs) {
				arc.destroy();
			}
			
			// Manage activity exit
			
			needlessArcs.clear();
			for (Arc arc : entry.getKey().getParent().getOutgoingArcs()) {
				// We use getParent(), because this IRSActivity contains incoming/outgoing arcs from resource - we
				// don't need that in this place.
				IRSPlace targetPlace = Converter.asIRSPlace(arc.getPlace());
				List<IRSNodeInterface> atomicSource = getAtomicOutputChildren(entry.getKey());
				List<IRSNodeInterface> atomicTarget = getAtomicInputChildren(targetPlace);
				// Delete all connections between these atomic nodes.
				// These connections will be replaced with Activity structure.

				// Get arcs to be deleted
				needlessArcs.addAll(getNeedlessArcs(atomicSource, atomicTarget));

				// Adding source activity structure (before firing)
				for (IRSNodeInterface source : atomicSource) {
					IRSTransition atomicSourceTransition = Converter.asIRSTransition(source);
					Transition atomicTransition = transitionsMap.get(atomicSourceTransition);
					if (atomicTransition == null) 
						throw new InternalAnalysisException(Cause.ATOMIC_TRANSITION_NOT_CREATED, 
								atomicSourceTransition.toString());
					addArc(atomicTransition, entry.getValue().getAfterWork(), arc.getWeight());
				}
				// Adding source activity structure (after firing)
				for (IRSNodeInterface target : atomicTarget) {
					IRSPlace atomicTargetPlace = Converter.asIRSPlace(target);
					Place atomicPlace = placesMap.get(atomicTargetPlace);
					if (atomicPlace == null) 
						throw new InternalAnalysisException(Cause.ATOMIC_PLACE_NOT_CREATED, 
								atomicTargetPlace.toString());
					addArc(entry.getValue().getEnd(), atomicPlace, arc.getWeight());
				}
			}
			
			for (Arc arc : needlessArcs) {
				arc.destroy();
			}
			
			// Link critical sections to mutex
			
			addArc(getMutex(), entry.getValue().getBegin());
			addArc(entry.getValue().getEndHiring(), getMutex());

			addArc(getMutex(), entry.getValue().getBeginFiring());
			addArc(entry.getValue().getEnd(), getMutex());
		}		
	}
	

	private List<Arc> getNeedlessArcs(List<IRSNodeInterface> atomicSource,
			List<IRSNodeInterface> atomicTarget) {
		Map<IRSPlace, Place> placesMap = getAtomicNet().getPlacesMap();
		Map<IRSTransition, Transition> transitionsMap = getAtomicNet().getTransitionsMap();
		List<Arc> arcsToDestroy = new LinkedList<Arc>();
		for (IRSNodeInterface irsSource : atomicSource) {
			INode source;
			if (irsSource instanceof IRSPlace) {
				source = placesMap.get(irsSource);
			} else {
				source = transitionsMap.get(irsSource);
			}
			for (IRSNodeInterface irsTarget : atomicTarget) {
				INode target;
				if (irsTarget instanceof IRSPlace) {
					target = placesMap.get(irsTarget);
				} else {
					target = transitionsMap.get(irsTarget);
				}
				for (Arc arc : target.getIncomingArcs()) {
					if (arc.getSource().equals(source)) {
						arcsToDestroy.add(arc);
					}
				}
			}
		}
		return arcsToDestroy;
	}
	
	private void linkActivitiesToResources() {
		//Map<IRSPlace, Place> placesMap = getAtomicNet().getPlacesMap();
		//Map<IRSTransition, Transition> transitionsMap = getAtomicNet().getTransitionsMap();
		
		for (Entry<IRSActivity, ActivitySubnet> entry : getActivityMap().entrySet()) {
			List<IRSResourceSet> resourcesSets = getResources(entry.getKey());
			for (IRSResourceSet resourceSet : resourcesSets) {
				// Here we connect an activity with one resource set
				HiringSubnet hiring = new HiringSubnet(
						entry.getKey(), resourceSet);
				
				loadNet(hiring);
				
				// Linking resource places of hiring
				ActivitySubnet subnet = entry.getValue();
				for (Entry<Resource, Place> resourcePlaceEntry : hiring.getResources().entrySet()) {
					addArc(subnet.getBegin(), resourcePlaceEntry.getValue());
				}
				
				// Linking hiring steps (in and out)
				Requirement firstRequirement = hiring.getFirstRequirement();
				Requirement lastRequirement = hiring.getLastRequirement();
				Place firstReqPlace = hiring.getRequirements().get(firstRequirement);
				// In
				addArc(subnet.getBegin(), firstReqPlace);
				// Out
				Map<Resource, Transition> lastRequirementMap = hiring.getHireTransitions().get(lastRequirement);
				for (Entry<Resource, Transition> transitionEntry : lastRequirementMap.entrySet()) {
					addArc(transitionEntry.getValue(), subnet.getAllHired());
				}
			}
		}
	}
	
	private List<IRSResourceSet> getResources(IRSActivity activity) {
		List<IRSResourceSet> list = new LinkedList<IRSResourceSet>();
		List<IRSResourceSet> targetResources = new LinkedList<IRSResourceSet>();
		for (Arc arc : activity.getOutgoingArcs()) {
			if (arc.getTarget() instanceof IRSResourceSet) {
				targetResources.add(Converter.asIRSResourceSet(arc.getTarget()));
			}
		}
		for (Arc arc : activity.getIncomingArcs()) {
			if (arc.getSource() instanceof IRSResourceSet) {
				IRSResourceSet resourceSet = Converter.asIRSResourceSet(arc.getSource());
				if (targetResources.contains(resourceSet)) {
					list.add(resourceSet);
				}
			}
		}
		return list;
	}
	
	private List<IRSNodeInterface> getAtomicInputChildren(IRSNodeInterface node) {
		List<IRSNodeInterface> nodes;
		if (node.isAtomic()) {
			nodes = Collections.singletonList(node);
		} else {
			nodes = new LinkedList<IRSNodeInterface>();
			for (IRSNodeInterface child : node.getInputChildren()) {
				nodes.addAll(getAtomicInputChildren(child));
			}
		}
		return nodes;
	}

	private List<IRSNodeInterface> getAtomicOutputChildren(IRSNodeInterface node) {
		List<IRSNodeInterface> nodes;
		if (node.isAtomic()) {
			nodes = Collections.singletonList(node);
		} else {
			nodes = new LinkedList<IRSNodeInterface>();
			for (IRSNodeInterface child : node.getOutputChildren()) {
				nodes.addAll(getAtomicOutputChildren(child));
			}
		}
		return nodes;
	}
	
	private Set<IRSActivity> getActivities(IRSNodeInterface node) {
		// FIXME : powinno inaczej działać - activity może mieć w sobie coś
		Set<IRSActivity> activities = new HashSet<IRSActivity>();
		if (node instanceof IRSActivity) activities.add((IRSActivity)node);
		for (IRSNodeInterface child : node.getChildren()) {
			activities.addAll(getActivities(child));
		}
		return activities;
	}
	
}
